/*    This file is part of ilk.
 *
 *    Ilk is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    Ilk is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public License
 *    along with ilk.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <dirent.h>
#include <inttypes.h>
#include <rid.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>

#define STYLE_9
#include <fn85.h>
#include <rid_fn85.h>

#include <unfy/unfy.h>
#include <unfy/unfp.h>

#include <rls/rls.h>
#include <rls/rlsp.h>
#include <rls/rlsr.h>

#define NIT_SHORT_NAMES
#include <nit/list.h>
#include <nit/set.h>
#include <nit/map.h>
#include <nit/err.h>
#include <nit/file.h>
#include <nit/buf.h>
#include <nit/gap.h>
#include <nit/crs.h>

#include "../utilize.h"
#include "../front.h"
#include "acts.h"

#define pass(str) (sizeof(str) - 1)

typedef enum {
	ILK_NO_CMD,
	ILK_VIEW,
	ILK_SAVE,
	ILK_COUNT,
	ILK_RECALL,
	ILK_TEST,
	ILK_PROD,
	ILK_MODE,
	ILK_ACT,
	ILK_DEPS,
} Ilk_cmd;

void
more_print(void *extra)
{
	Ilk_more *more = extra;

	printf(".%" PRIu64, more->srch_cnt);
}

#define WORD_PARSE(LIT, ENUM_VAL)		\
	if (!strncmp(str, LIT, pass(LIT))) {	\
		*pos += pass(LIT);		\
		return ENUM_VAL;		\
	}

Ilk_cmd
ilk_cmd_parse(const char *str, size_t *pos)
{
	switch (*str) {
	case 'a':
		WORD_PARSE("act", ILK_ACT);
		break;
	case 'v':
		WORD_PARSE("view", ILK_VIEW);
		break;
	case 's':
		WORD_PARSE("save", ILK_SAVE);
		break;
	case 'c':
		WORD_PARSE("count", ILK_COUNT);
		break;
	case 'r':
		WORD_PARSE("recall", ILK_RECALL);
		break;
	case 't':
		WORD_PARSE("test", ILK_TEST);
		break;
	case 'p':
		WORD_PARSE("prod", ILK_PROD);
		break;
	case 'm':
		WORD_PARSE("mode", ILK_MODE);
		break;
	case 'd':
		WORD_PARSE("deps", ILK_DEPS);
		break;
	}

	return ILK_NO_CMD;
}

void
print_view(Ilk_sys *sys)
{
	Nit_iter iter;

	if (!sys->grps.entry_num) {
		printf("Nothing here\n");
		return;
	}

	iter_init(&iter, &sys->grps);

	do {
		Ilk_grp *grp = iter_val(&iter);

		rlsp_rule_print(grp->rules, more_print);
	} while (iter_next(&iter));
}

static Unfy_stat
ilk_recall(Ilk_sys *sys, char *str, size_t pos)
{
	const char *error;
	uint64_t srch_cnt;
	Rls_srch *srch = NULL;

	if (unfp_space(str + pos, &pos, &error) < 0) {
		err_cust(&sys->lib->err, error);
		goto err_repl;
	}

	errno = 0;

	if (sscanf(str + pos, "%" PRIu64, &srch_cnt) < 1) {
		if (errno)
			err_std(&sys->lib->err);
		else if (str[pos] == '\0')
			err_cust(&sys->lib->err, "Missing digit");
		else
			err_cust(&sys->lib->err, "Invalid digit");

		goto err_repl;
	}

	if (ilk_srch_get(&srch, srch_cnt, sys) < 0)
		goto err_get;

	rlsp_srch_print(srch, 1);
	/* rls_recycle_srch(&sys->lib->rec, srch); */
	return UNFY_YES;
err_repl:
	err_where(&sys->lib->err, "repl");
	err_pos(&sys->lib->err, pos);
err_get:
	err_under(&sys->lib->err, pos + sizeof("* ") - 1);
	return UNFY_ERR;
}

static Ilk_act_num
ilk_act_name(Ilk_sys *sys, char *str, size_t *pos)
{
	const char *error;

	if (unfp_space(str + *pos, pos, &error) < 0) {
		err_cust(&sys->lib->err, error);
		goto err;
	}

	str += *pos;

	switch (*str) {
	case 'a':
		WORD_PARSE("add", ILK_ACT_ADD);
		break;
	case 'g':
		WORD_PARSE("gensym", ILK_ACT_GENSYM);
		break;
	}

	err_cust(&sys->lib->err, "Unknown act name");
err:
	err_pos(&sys->lib->err, *pos);
	err_under(&sys->lib->err, *pos + sizeof("* ") - 1);
	err_where(&sys->lib->err, "repl");
	return ILK_ACT_NO;
}

static Unfy_stat
repl_act(Ilk_sys *sys, char *str, size_t pos)
{
	Ilk_act_num act = ilk_act_name(sys, str, &pos);
	Ilk_grp *grp;

	if (act == ILK_ACT_NO || !(grp = ilk_act_add2(sys, act)))
		return UNFY_ERR;

	return UNFY_YES;
}

static void
deps_print(Nit_gap *deps)
{
	Nit_crs crs;
	uint64_t srch_cnt;

	crs_init(&crs, deps, 0);

	while (crs_movef(&crs, sizeof(srch_cnt)) >= 0) {
		crs_read(&crs, &srch_cnt, sizeof(srch_cnt));
		srch_cnt = le64toh(srch_cnt);
		printf("%" PRIu64 " ", srch_cnt);
	}

	printf("\n");
}

static Unfy_stat
repl_deps(Ilk_sys *sys, char *str, size_t pos)
{
	size_t tmp;
	Rid id;
	const char *error;
	Ilk_meta meta;

	str += pos;
	tmp = pos;

	if (unfp_space(str, &pos, &error) < 0) {
		err_cust(&sys->lib->err, error);
		goto err_space;
	}

	str += pos - tmp;
	tmp = pos;

	if (rid_fn85_parse(id, str, &pos, &error) != FN85_OKAY)
		goto id_err;

	if (ilk_meta_load(&meta, id, sys) < 0)
		return UNFY_ERR;

	deps_print(&meta.deps);
	ilk_meta_dispose(&meta);
	return UNFY_YES;
id_err:
	err_cust(&sys->lib->err, error);
err_space:
	err_under(&sys->lib->err, pos + sizeof("* ") - 1);
	err_where(&sys->lib->err, "repl");
	err_pos(&sys->lib->err, pos);
	return UNFY_ERR;
}

static Unfy_stat
ilk_lsn(Rlsr_lsnr *lsnr, Rls_rule **rules, Rls_srch **srch, char **str,
	size_t *size, Rls_info *info, void *extra, Rls_recycle *rec)
{
	Ilk_sys *sys = extra;

	switch (ilk_cmd_parse(*str, &lsnr->pos)) {
	case ILK_SAVE:
		if (ilk_sys_save(sys) < 0)
			return UNFY_ERR;
		else
			printf("saved\n");

		return UNFY_YES;
	case ILK_VIEW:
		print_view(sys);
	        return UNFY_YES;
	case ILK_COUNT:
		printf("%" PRIu64 "\n", sys->srch_cnt + 1);
	        return UNFY_YES;
	case ILK_RECALL:
		return ilk_recall(sys, *str, lsnr->pos);
	case ILK_TEST:
		if (sys->lib->testing)
			printf("Already in testing mode\n");
		else
			printf("Changed to testing mode\n");

		sys->lib->testing = 1;
		return UNFY_YES;
	case ILK_PROD:
		if (!sys->lib->testing)
			printf("Already in production mode\n");
		else
			printf("Changed to production mode\n");

		sys->lib->testing = 0;
		return UNFY_YES;
	case ILK_MODE:
		if (sys->lib->testing)
			printf("In testing mode\n");
		else
			printf("In production mode\n");

		return UNFY_YES;
	case ILK_ACT:
		return repl_act(sys, *str, lsnr->pos);
	case ILK_DEPS:
		return repl_deps(sys, *str, lsnr->pos);
	case ILK_NO_CMD:
	        break;
	}

        if (rlsr_default_lsn(lsnr, rules, srch, str,
			     size, info, extra, rec) == UNFY_ERR) {
		if (!sys->lib->err.occured) {
			err_cust(&sys->lib->err, lsnr->error_str);
			err_pos(&sys->lib->err, lsnr->pos);
			err_under(&sys->lib->err, lsnr->pos + sizeof("* ") - 1);
			err_where(&sys->lib->err, "repl");
		}

		return UNFY_ERR;
	}

	return UNFY_YES;
}

static void
handle_invalid_rule(Nit_err *err, Rlsr_stmnt *stmnt)
{
	if (!ilk_rule_valid(stmnt->term, stmnt->list))
		return;

	err_where(err, "repl");
	err_pos(err, stmnt->term_pos);
	err_under(err, stmnt->term_pos + sizeof("* ") - 1);
}

int
ilk_assert(Rlsr_lsnr *lsnr, Rlsr_stmnt *stmnt,
	   Rls_rule **rules, void *extra, Rls_recycle *rec)
{
	Ilk_sys *sys = extra;
	Ilk_grp *grp = ilk_rule_add(sys, stmnt->id, stmnt->term,
				    stmnt->list, 0);

	if (!grp) {
		handle_invalid_rule(&sys->lib->err, stmnt);
	        return -1;
	}

        *rules = grp->rules;
	return 0;
}

Unfy_stat
ilk_search(Rlsr_lsnr *lsnr, Rlsr_stmnt *stmnt, Rls_srch **srch_ref,
	   Rls_rule **rules, Rls_info *info, void *extra,
	   Rls_recycle *rec)
{
	Ilk_sys *sys = extra;
	Ilk_grp *grp = NULL;
	Unfy_stat stat;

	if (sys->lib->testing)
		stat = ilk_srch(sys, srch_ref, stmnt->term,
				&stmnt->limit, info);
	else
		stat = ilk_srch_n_save(sys, srch_ref, stmnt->term,
				       &stmnt->limit, info);

	switch (stat) {
	case UNFY_NO:
		break;
	case UNFY_YES:
		/* This should never fail b/c search */
	        grp = ilk_grp_get_term(sys, stmnt->term, &stat);
		break;
	case UNFY_ERR:
		handle_invalid_rule(&sys->lib->err, stmnt);
		break;
	}

	*rules = grp ? grp->rules : NULL;
	return stat;
}

void
ilk_err(Rlsr_lsnr *lsnr, void *extra)
{
	Ilk_sys *sys = extra;

	err_print(&sys->lib->err);
	sys->lib->err.occured = 0;
}

void
ilk_repl(Ilk_sys *sys)
{
	Rls_rule *rules = NULL;
	Rlsr_lsnr lsnr;

	rlsr_lsnr_init(&lsnr, ilk_lsn, ilk_assert, ilk_search,
		       ilk_err, sys->lib->bend, NULL, 0);

	rlsr_repl(&rules, &lsnr, sys, &sys->lib->rec);

	if (!sys->lib->testing)
		ilk_sys_save(sys);
}

int
main(int argc, char *argv[])
{
	Ilk_lib lib;
	Ilk_sys *sys;

	ilk_lib_init(&lib, ILK_ACT_MAX, ilk_acts);

	if (!(sys = ilk_sys(&lib, "./"))) {
		err_print(&lib.err);
		return 0;
	}

	ilk_repl(sys);
	ilk_sys_rec(sys);
	ilk_lib_dispose(&lib);
	return 0;
}
